/***********************************************************************
*
* Copyright (c) 2012-2022 Barbara Geller
* Copyright (c) 2012-2022 Ansel Sermersheim
*
* Copyright (c) 2015 The Qt Company Ltd.
* Copyright (c) 2012-2016 Digia Plc and/or its subsidiary(-ies).
* Copyright (c) 2008-2012 Nokia Corporation and/or its subsidiary(-ies).
*
* This file is part of CopperSpice.
*
* CopperSpice is free software. You can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* CopperSpice is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* https://www.gnu.org/licenses/
*
***********************************************************************/

#include <ctype.h>

#include <qcorecmdlineargs_p.h>
#include <qcoreapplication.h>
#include <qcoreapplication_p.h>
#include <qfileinfo.h>
#include <qmutex.h>
#include <qstringlist.h>
#include <qstring16.h>
#include <qt_windows.h>
#include <qthread_p.h>
#include <qvector.h>

bool usingWinMain = false;  // whether the qWinMain() is used or not
int appCmdShow = 0;

Q_CORE_EXPORT HINSTANCE qWinAppInst()                      // get Windows app handle
{
   return GetModuleHandle(nullptr);
}

Q_CORE_EXPORT HINSTANCE qWinAppPrevInst()                  // get Windows prev app handle
{
   return nullptr;
}

Q_CORE_EXPORT int qWinAppCmdShow()                         // get main window show command
{
   STARTUPINFO startupInfo;
   GetStartupInfo(&startupInfo);

   return (startupInfo.dwFlags & STARTF_USESHOWWINDOW)
          ? startupInfo.wShowWindow : SW_SHOWDEFAULT;
}

Q_CORE_EXPORT QString qAppFileName()
{
   // We do MAX_PATH + 2 here, and request with MAX_PATH + 1, so we can handle all paths
   // up to, and including MAX_PATH size perfectly fine with string termination, as well
   // as easily detect if the file path is indeed larger than MAX_PATH, in which case we
   // need to use the heap instead.

   // This is a work-around, since contrary to what the
   // MSDN documentation states, GetModuleFileName sometimes doesn't set the
   // ERROR_INSUFFICIENT_BUFFER error number, and we thus cannot rely on this value if

   // GetModuleFileName(0, buffer, MAX_PATH) == MAX_PATH.
   // GetModuleFileName(0, buffer, MAX_PATH + 1) == MAX_PATH just means we hit the normal
   // file path limit, and we handle it normally, if the result is MAX_PATH + 1, we use
   // heap (even if the result _might_ be exactly MAX_PATH + 1).

   // get application file name
   std::wstring buffer(MAX_PATH + 2, L'\0');

   DWORD v = GetModuleFileName(nullptr, &buffer[0], MAX_PATH + 1);

   if (v == 0) {
      return QString();

   } else if (v <= MAX_PATH) {
      return QString::fromStdWString(buffer, v);

   }

   // MAX_PATH sized buffer was not large enough to contain the full path, use heap
   int i = 1;

   size_t size;

   do {
      ++i;

      size = MAX_PATH * i;
      buffer.resize(size + 1);

      v = GetModuleFileName(nullptr, &buffer[0], size);

   } while (v == size);

   QString res = QString::fromStdWString(buffer, v);

   return res;
}

QString QCoreApplicationPrivate::appName() const
{
   return QFileInfo(qAppFileName()).baseName();
}

class QWinMsgHandlerCriticalSection
{
   CRITICAL_SECTION cs;

 public:
   QWinMsgHandlerCriticalSection() {
      InitializeCriticalSection(&cs);
   }

   ~QWinMsgHandlerCriticalSection() {
      DeleteCriticalSection(&cs);
   }

   void lock() {
      EnterCriticalSection(&cs);
   }
   void unlock() {
      LeaveCriticalSection(&cs);
   }
};

Q_CORE_EXPORT void qWinMsgHandler(QtMsgType t, QStringView str)
{
   // OutputDebugString is not threadsafe.

   // cannot use QMutex here, because qWarning()s in the QMutex
   // implementation may cause this function to recurse

   (void) t;

   static QWinMsgHandlerCriticalSection staticCriticalSection;

   if (str.isEmpty()) {
      static QString tmp = "(null)";
      str = tmp;
   }

   staticCriticalSection.lock();

   QString16 tmpString(str.cbegin(), str.cend());

   tmpString += '\n';
   OutputDebugString((wchar_t *)tmpString.constData());

   staticCriticalSection.unlock();
}


/*****************************************************************************
  qWinMain() - Initializes Windows. Called from WinMain() in qtmain_win.cpp
 *****************************************************************************/


Q_CORE_EXPORT void qWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParam,
                  int cmdShow, int &argc, QVector<char *> &argv)
{
   static bool already_called = false;

   if (already_called) {
      qWarning("Internal error: qWinMain should be called only once");
      return;
   }

   already_called = true;
   usingWinMain = true;

   // Install default debug handler
   csInstallMsgHandler(qWinMsgHandler);

   // Create command line
   argv = qWinCmdLine<char>(cmdParam, int(strlen(cmdParam)), argc);

   appCmdShow = cmdShow;

   // Ignore Windows parameters
   Q_UNUSED(instance);
   Q_UNUSED(prevInstance);
}

void QCoreApplicationPrivate::removePostedTimerEvent(QObject *object, int timerId)
{
   QThreadData *data = CSInternalThreadData::get_m_ThreadData(object);

   QMutexLocker locker(&data->postEventList.mutex);

   if (data->postEventList.size() == 0) {
      return;
   }

   for (int i = 0; i < data->postEventList.size(); ++i) {
      const QPostEvent &pe = data->postEventList.at(i);

      if (pe.receiver == object
            && pe.event
            && (pe.event->type() == QEvent::Timer || pe.event->type() == QEvent::ZeroTimerEvent)

            && static_cast<QTimerEvent *>(pe.event)->timerId() == timerId) {
         CSInternalEvents::decr_PostedEvents(pe.receiver);
         pe.event->posted = false;

         delete pe.event;
         const_cast<QPostEvent &>(pe).event = nullptr;
         return;
      }
   }
}

#if defined(Q_OS_WIN)

// Convenience functions for convert WM_* messages into human readable strings,
// including a nifty QDebug operator<< for simpel QDebug() << msg output.

#include <windowsx.h>
#include <qdebug.h>

#if !defined(GET_X_LPARAM)
#  define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#  define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
#endif

// The values below should never change. Note that none of the usual
// WM_...FIRST & WM_...LAST values are in the list, as they normally have other
// WM_... representations

struct KnownWM {
   uint WM;
   const char *str;
} knownWM[] = {
   { 0x0000, "WM_NULL" },
   { 0x0001, "WM_CREATE" },
   { 0x0002, "WM_DESTROY" },
   { 0x0003, "WM_MOVE" },
   { 0x0005, "WM_SIZE" },
   { 0x0006, "WM_ACTIVATE" },
   { 0x0007, "WM_SETFOCUS" },
   { 0x0008, "WM_KILLFOCUS" },
   { 0x000A, "WM_ENABLE" },
   { 0x000B, "WM_SETREDRAW" },
   { 0x000C, "WM_SETTEXT" },
   { 0x000D, "WM_GETTEXT" },
   { 0x000E, "WM_GETTEXTLENGTH" },
   { 0x000F, "WM_PAINT" },
   { 0x0010, "WM_CLOSE" },
   { 0x0011, "WM_QUERYENDSESSION" },
   { 0x0013, "WM_QUERYOPEN" },
   { 0x0016, "WM_ENDSESSION" },
   { 0x0012, "WM_QUIT" },
   { 0x0014, "WM_ERASEBKGND" },
   { 0x0015, "WM_SYSCOLORCHANGE" },
   { 0x0018, "WM_SHOWWINDOW" },
   { 0x001A, "WM_WININICHANGE" },
   { 0x001B, "WM_DEVMODECHANGE" },
   { 0x001C, "WM_ACTIVATEAPP" },
   { 0x001D, "WM_FONTCHANGE" },
   { 0x001E, "WM_TIMECHANGE" },
   { 0x001F, "WM_CANCELMODE" },
   { 0x0020, "WM_SETCURSOR" },
   { 0x0021, "WM_MOUSEACTIVATE" },
   { 0x0022, "WM_CHILDACTIVATE" },
   { 0x0023, "WM_QUEUESYNC" },
   { 0x0024, "WM_GETMINMAXINFO" },
   { 0x0026, "WM_PAINTICON" },
   { 0x0027, "WM_ICONERASEBKGND" },
   { 0x0028, "WM_NEXTDLGCTL" },
   { 0x002A, "WM_SPOOLERSTATUS" },
   { 0x002B, "WM_DRAWITEM" },
   { 0x002C, "WM_MEASUREITEM" },
   { 0x002D, "WM_DELETEITEM" },
   { 0x002E, "WM_VKEYTOITEM" },
   { 0x002F, "WM_CHARTOITEM" },
   { 0x0030, "WM_SETFONT" },
   { 0x0031, "WM_GETFONT" },
   { 0x0032, "WM_SETHOTKEY" },
   { 0x0033, "WM_GETHOTKEY" },
   { 0x0037, "WM_QUERYDRAGICON" },
   { 0x0039, "WM_COMPAREITEM" },
   { 0x003D, "WM_GETOBJECT" },
   { 0x0041, "WM_COMPACTING" },
   { 0x0044, "WM_COMMNOTIFY" },
   { 0x0046, "WM_WINDOWPOSCHANGING" },
   { 0x0047, "WM_WINDOWPOSCHANGED" },
   { 0x0048, "WM_POWER" },
   { 0x004A, "WM_COPYDATA" },
   { 0x004B, "WM_CANCELJOURNAL" },
   { 0x004E, "WM_NOTIFY" },
   { 0x0050, "WM_INPUTLANGCHANGEREQUEST" },
   { 0x0051, "WM_INPUTLANGCHANGE" },
   { 0x0052, "WM_TCARD" },
   { 0x0053, "WM_HELP" },
   { 0x0054, "WM_USERCHANGED" },
   { 0x0055, "WM_NOTIFYFORMAT" },
   { 0x007B, "WM_CONTEXTMENU" },
   { 0x007C, "WM_STYLECHANGING" },
   { 0x007D, "WM_STYLECHANGED" },
   { 0x007E, "WM_DISPLAYCHANGE" },
   { 0x007F, "WM_GETICON" },
   { 0x0080, "WM_SETICON" },
   { 0x0081, "WM_NCCREATE" },
   { 0x0082, "WM_NCDESTROY" },
   { 0x0083, "WM_NCCALCSIZE" },
   { 0x0084, "WM_NCHITTEST" },
   { 0x0085, "WM_NCPAINT" },
   { 0x0086, "WM_NCACTIVATE" },
   { 0x0087, "WM_GETDLGCODE" },
   { 0x0088, "WM_SYNCPAINT" },
   { 0x00A0, "WM_NCMOUSEMOVE" },
   { 0x00A1, "WM_NCLBUTTONDOWN" },
   { 0x00A2, "WM_NCLBUTTONUP" },
   { 0x00A3, "WM_NCLBUTTONDBLCLK" },
   { 0x00A4, "WM_NCRBUTTONDOWN" },
   { 0x00A5, "WM_NCRBUTTONUP" },
   { 0x00A6, "WM_NCRBUTTONDBLCLK" },
   { 0x00A7, "WM_NCMBUTTONDOWN" },
   { 0x00A8, "WM_NCMBUTTONUP" },
   { 0x00A9, "WM_NCMBUTTONDBLCLK" },
   { 0x00AB, "WM_NCXBUTTONDOWN" },
   { 0x00AC, "WM_NCXBUTTONUP" },
   { 0x00AD, "WM_NCXBUTTONDBLCLK" },
   { 0x00FF, "WM_INPUT" },
   { 0x0100, "WM_KEYDOWN" },
   { 0x0101, "WM_KEYUP" },
   { 0x0102, "WM_CHAR" },
   { 0x0103, "WM_DEADCHAR" },
   { 0x0104, "WM_SYSKEYDOWN" },
   { 0x0105, "WM_SYSKEYUP" },
   { 0x0106, "WM_SYSCHAR" },
   { 0x0107, "WM_SYSDEADCHAR" },
   { 0x0109, "WM_UNICHAR" },
   { 0x010D, "WM_IME_STARTCOMPOSITION" },
   { 0x010E, "WM_IME_ENDCOMPOSITION" },
   { 0x010F, "WM_IME_COMPOSITION" },
   { 0x0110, "WM_INITDIALOG" },
   { 0x0111, "WM_COMMAND" },
   { 0x0112, "WM_SYSCOMMAND" },
   { 0x0113, "WM_TIMER" },
   { 0x0114, "WM_HSCROLL" },
   { 0x0115, "WM_VSCROLL" },
   { 0x0116, "WM_INITMENU" },
   { 0x0117, "WM_INITMENUPOPUP" },
   { 0x011F, "WM_MENUSELECT" },
   { 0x0120, "WM_MENUCHAR" },
   { 0x0121, "WM_ENTERIDLE" },
   { 0x0122, "WM_MENURBUTTONUP" },
   { 0x0123, "WM_MENUDRAG" },
   { 0x0124, "WM_MENUGETOBJECT" },
   { 0x0125, "WM_UNINITMENUPOPUP" },
   { 0x0126, "WM_MENUCOMMAND" },
   { 0x0127, "WM_CHANGEUISTATE" },
   { 0x0128, "WM_UPDATEUISTATE" },
   { 0x0129, "WM_QUERYUISTATE" },
   { 0x0132, "WM_CTLCOLORMSGBOX" },
   { 0x0133, "WM_CTLCOLOREDIT" },
   { 0x0134, "WM_CTLCOLORLISTBOX" },
   { 0x0135, "WM_CTLCOLORBTN" },
   { 0x0136, "WM_CTLCOLORDLG" },
   { 0x0137, "WM_CTLCOLORSCROLLBAR" },
   { 0x0138, "WM_CTLCOLORSTATIC" },
   { 0x0200, "WM_MOUSEMOVE" },
   { 0x0201, "WM_LBUTTONDOWN" },
   { 0x0202, "WM_LBUTTONUP" },
   { 0x0203, "WM_LBUTTONDBLCLK" },
   { 0x0204, "WM_RBUTTONDOWN" },
   { 0x0205, "WM_RBUTTONUP" },
   { 0x0206, "WM_RBUTTONDBLCLK" },
   { 0x0207, "WM_MBUTTONDOWN" },
   { 0x0208, "WM_MBUTTONUP" },
   { 0x0209, "WM_MBUTTONDBLCLK" },
   { 0x020A, "WM_MOUSEWHEEL" },
   { 0x020B, "WM_XBUTTONDOWN" },
   { 0x020C, "WM_XBUTTONUP" },
   { 0x020D, "WM_XBUTTONDBLCLK" },
   { 0x020E, "WM_MOUSEHWHEEL" },
   { 0x0210, "WM_PARENTNOTIFY" },
   { 0x0211, "WM_ENTERMENULOOP" },
   { 0x0212, "WM_EXITMENULOOP" },
   { 0x0213, "WM_NEXTMENU" },
   { 0x0214, "WM_SIZING" },
   { 0x0215, "WM_CAPTURECHANGED" },
   { 0x0216, "WM_MOVING" },
   { 0x0218, "WM_POWERBROADCAST" },
   { 0x0219, "WM_DEVICECHANGE" },
   { 0x0220, "WM_MDICREATE" },
   { 0x0221, "WM_MDIDESTROY" },
   { 0x0222, "WM_MDIACTIVATE" },
   { 0x0223, "WM_MDIRESTORE" },
   { 0x0224, "WM_MDINEXT" },
   { 0x0225, "WM_MDIMAXIMIZE" },
   { 0x0226, "WM_MDITILE" },
   { 0x0227, "WM_MDICASCADE" },
   { 0x0228, "WM_MDIICONARRANGE" },
   { 0x0229, "WM_MDIGETACTIVE" },
   { 0x0230, "WM_MDISETMENU" },
   { 0x0231, "WM_ENTERSIZEMOVE" },
   { 0x0232, "WM_EXITSIZEMOVE" },
   { 0x0233, "WM_DROPFILES" },
   { 0x0234, "WM_MDIREFRESHMENU" },
   { 0x0281, "WM_IME_SETCONTEXT" },
   { 0x0282, "WM_IME_NOTIFY" },
   { 0x0283, "WM_IME_CONTROL" },
   { 0x0284, "WM_IME_COMPOSITIONFULL" },
   { 0x0285, "WM_IME_SELECT" },
   { 0x0286, "WM_IME_CHAR" },
   { 0x0288, "WM_IME_REQUEST" },
   { 0x0290, "WM_IME_KEYDOWN" },
   { 0x0291, "WM_IME_KEYUP" },
   { 0x02A0, "WM_NCMOUSEHOVER" },
   { 0x02A1, "WM_MOUSEHOVER" },
   { 0x02A2, "WM_NCMOUSELEAVE" },
   { 0x02A3, "WM_MOUSELEAVE" },
   { 0x02B1, "WM_WTSSESSION_CHANGE" },
   { 0x02C0, "WM_TABLET_FIRST" },
   { 0x02C1, "WM_TABLET_FIRST + 1" },
   { 0x02C2, "WM_TABLET_FIRST + 2" },
   { 0x02C3, "WM_TABLET_FIRST + 3" },
   { 0x02C4, "WM_TABLET_FIRST + 4" },
   { 0x02C5, "WM_TABLET_FIRST + 5" },
   { 0x02C6, "WM_TABLET_FIRST + 6" },
   { 0x02C7, "WM_TABLET_FIRST + 7" },
   { 0x02C8, "WM_TABLET_FIRST + 8" },
   { 0x02C9, "WM_TABLET_FIRST + 9" },
   { 0x02CA, "WM_TABLET_FIRST + 10" },
   { 0x02CB, "WM_TABLET_FIRST + 11" },
   { 0x02CC, "WM_TABLET_FIRST + 12" },
   { 0x02CD, "WM_TABLET_FIRST + 13" },
   { 0x02CE, "WM_TABLET_FIRST + 14" },
   { 0x02CF, "WM_TABLET_FIRST + 15" },
   { 0x02D0, "WM_TABLET_FIRST + 16" },
   { 0x02D1, "WM_TABLET_FIRST + 17" },
   { 0x02D2, "WM_TABLET_FIRST + 18" },
   { 0x02D3, "WM_TABLET_FIRST + 19" },
   { 0x02D4, "WM_TABLET_FIRST + 20" },
   { 0x02D5, "WM_TABLET_FIRST + 21" },
   { 0x02D6, "WM_TABLET_FIRST + 22" },
   { 0x02D7, "WM_TABLET_FIRST + 23" },
   { 0x02D8, "WM_TABLET_FIRST + 24" },
   { 0x02D9, "WM_TABLET_FIRST + 25" },
   { 0x02DA, "WM_TABLET_FIRST + 26" },
   { 0x02DB, "WM_TABLET_FIRST + 27" },
   { 0x02DC, "WM_TABLET_FIRST + 28" },
   { 0x02DD, "WM_TABLET_FIRST + 29" },
   { 0x02DE, "WM_TABLET_FIRST + 30" },
   { 0x02DF, "WM_TABLET_LAST" },
   { 0x0300, "WM_CUT" },
   { 0x0301, "WM_COPY" },
   { 0x0302, "WM_PASTE" },
   { 0x0303, "WM_CLEAR" },
   { 0x0304, "WM_UNDO" },
   { 0x0305, "WM_RENDERFORMAT" },
   { 0x0306, "WM_RENDERALLFORMATS" },
   { 0x0307, "WM_DESTROYCLIPBOARD" },
   { 0x0308, "WM_DRAWCLIPBOARD" },
   { 0x0309, "WM_PAINTCLIPBOARD" },
   { 0x030A, "WM_VSCROLLCLIPBOARD" },
   { 0x030B, "WM_SIZECLIPBOARD" },
   { 0x030C, "WM_ASKCBFORMATNAME" },
   { 0x030D, "WM_CHANGECBCHAIN" },
   { 0x030E, "WM_HSCROLLCLIPBOARD" },
   { 0x030F, "WM_QUERYNEWPALETTE" },
   { 0x0310, "WM_PALETTEISCHANGING" },
   { 0x0311, "WM_PALETTECHANGED" },
   { 0x0312, "WM_HOTKEY" },
   { 0x0317, "WM_PRINT" },
   { 0x0318, "WM_PRINTCLIENT" },
   { 0x0319, "WM_APPCOMMAND" },
   { 0x031A, "WM_THEMECHANGED" },
   { 0x0358, "WM_HANDHELDFIRST" },
   { 0x0359, "WM_HANDHELDFIRST + 1" },
   { 0x035A, "WM_HANDHELDFIRST + 2" },
   { 0x035B, "WM_HANDHELDFIRST + 3" },
   { 0x035C, "WM_HANDHELDFIRST + 4" },
   { 0x035D, "WM_HANDHELDFIRST + 5" },
   { 0x035E, "WM_HANDHELDFIRST + 6" },
   { 0x035F, "WM_HANDHELDLAST" },
   { 0x0360, "WM_AFXFIRST" },
   { 0x0361, "WM_AFXFIRST + 1" },
   { 0x0362, "WM_AFXFIRST + 2" },
   { 0x0363, "WM_AFXFIRST + 3" },
   { 0x0364, "WM_AFXFIRST + 4" },
   { 0x0365, "WM_AFXFIRST + 5" },
   { 0x0366, "WM_AFXFIRST + 6" },
   { 0x0367, "WM_AFXFIRST + 7" },
   { 0x0368, "WM_AFXFIRST + 8" },
   { 0x0369, "WM_AFXFIRST + 9" },
   { 0x036A, "WM_AFXFIRST + 10" },
   { 0x036B, "WM_AFXFIRST + 11" },
   { 0x036C, "WM_AFXFIRST + 12" },
   { 0x036D, "WM_AFXFIRST + 13" },
   { 0x036E, "WM_AFXFIRST + 14" },
   { 0x036F, "WM_AFXFIRST + 15" },
   { 0x0370, "WM_AFXFIRST + 16" },
   { 0x0371, "WM_AFXFIRST + 17" },
   { 0x0372, "WM_AFXFIRST + 18" },
   { 0x0373, "WM_AFXFIRST + 19" },
   { 0x0374, "WM_AFXFIRST + 20" },
   { 0x0375, "WM_AFXFIRST + 21" },
   { 0x0376, "WM_AFXFIRST + 22" },
   { 0x0377, "WM_AFXFIRST + 23" },
   { 0x0378, "WM_AFXFIRST + 24" },
   { 0x0379, "WM_AFXFIRST + 25" },
   { 0x037A, "WM_AFXFIRST + 26" },
   { 0x037B, "WM_AFXFIRST + 27" },
   { 0x037C, "WM_AFXFIRST + 28" },
   { 0x037D, "WM_AFXFIRST + 29" },
   { 0x037E, "WM_AFXFIRST + 30" },
   { 0x037F, "WM_AFXLAST" },
   { 0x0380, "WM_PENWINFIRST" },
   { 0x0381, "WM_PENWINFIRST + 1" },
   { 0x0382, "WM_PENWINFIRST + 2" },
   { 0x0383, "WM_PENWINFIRST + 3" },
   { 0x0384, "WM_PENWINFIRST + 4" },
   { 0x0385, "WM_PENWINFIRST + 5" },
   { 0x0386, "WM_PENWINFIRST + 6" },
   { 0x0387, "WM_PENWINFIRST + 7" },
   { 0x0388, "WM_PENWINFIRST + 8" },
   { 0x0389, "WM_PENWINFIRST + 9" },
   { 0x038A, "WM_PENWINFIRST + 10" },
   { 0x038B, "WM_PENWINFIRST + 11" },
   { 0x038C, "WM_PENWINFIRST + 12" },
   { 0x038D, "WM_PENWINFIRST + 13" },
   { 0x038E, "WM_PENWINFIRST + 14" },
   { 0x038F, "WM_PENWINLAST" },
   { 0x0400, "WM_USER" },
   { 0x8000, "WM_APP" },
   { 0,      nullptr }
}; // End of known messages

// Looks up the WM_ message in the table above
static const char *findWMstr(uint msg)
{
   uint i = 0;
   const char *result = nullptr;
   // Known WM_'s
   while (knownWM[i].str && (knownWM[i].WM != msg)) {
      ++i;
   }
   result = knownWM[i].str;
   return result;
};

// Convenience function for converting flags and values into readable strings
struct FLAG_STRING_STRUCT {
   uint value;
   const char *str;
};

FLAG_STRING_STRUCT FLAG_STRING(uint value = 0, const char *c = nullptr)
{
   FLAG_STRING_STRUCT s = {value, c};
   return s;
}

#define FLGSTR(x) FLAG_STRING(x, #x)

// Returns an ORed (" | ") together string for the flags active in the actual
// value. (...) must consist of FLAG_STRING, with a FLAG_STRING() as the last
// value in the list passed to the function
QString flagCheck(uint actual, ...)
{
   va_list ap;
   va_start(ap, actual);

   QString result;
   int count = 0;
   FLAG_STRING_STRUCT v;
   while ((v = va_arg(ap, FLAG_STRING_STRUCT)).str) {
      if ((actual & v.value) == v.value) {
         if (count++) {
            result += QLatin1String(" | ");
         }
         result += QString::fromLatin1(v.str);
      }
   }
   va_end(ap);
   return result;
};

// Returns the string representation of the value in 'actual'. (...) must
// consist of FLAG_STRING, with a FLAG_STRING() as the last value in the list
// passed to the function
QString valueCheck(uint actual, ...)
{
   va_list ap;
   va_start(ap, actual);

   QString result;
   FLAG_STRING_STRUCT v;
   while ((v = va_arg(ap, FLAG_STRING_STRUCT)).str && (actual != v.value))
      ;
   result = QString::fromLatin1(v.str);

   va_end(ap);
   return result;
};

// Returns a "human readable" string representation of the MSG and the information it points to
QString decodeMSG(const MSG &msg)
{
   const WPARAM wParam = msg.wParam;
   const LPARAM lParam = msg.lParam;

   QString wmmsg = QString::fromLatin1(findWMstr(msg.message));

   // Unknown WM_, so use number
   if (wmmsg.isEmpty()) {
      wmmsg = QString("WM_(%1)").formatArg(msg.message);
   }

   QString rawParameters = QString("hwnd(0x%1)").formatArg(reinterpret_cast<quintptr>(msg.hwnd), 8, 16, '0');

   // Custom WM_'s
   if (msg.message > WM_APP) {
      wmmsg = QString("WM_APP + %1").formatArg(msg.message - WM_APP);

   } else if (msg.message > WM_USER) {
      wmmsg = QString("WM_USER + %1").formatArg(msg.message - WM_USER);
   }

   QString parameters;

   switch (msg.message) {

#ifdef WM_ACTIVATE
      case WM_ACTIVATE: {
         QString activation = valueCheck(wParam,
                                         FLAG_STRING(WA_ACTIVE,      "Activate"),
                                         FLAG_STRING(WA_INACTIVE,    "Deactivate"),
                                         FLAG_STRING(WA_CLICKACTIVE, "Activate by mouseclick"),
                                         FLAG_STRING());

         parameters = QString("%1 Hwnd (0x%2)").formatArg(activation).formatArg(reinterpret_cast<quintptr>(msg.hwnd), 8, 16, '0');
      }

      break;
#endif

#ifdef WM_CAPTURECHANGED
      case WM_CAPTURECHANGED:
         parameters = QString("Hwnd gaining capture (0x%1)").formatArg(lParam, 8, 16, '0');
         break;
#endif

#ifdef WM_CREATE
      case WM_CREATE: {
         LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
         QString styles = flagCheck(lpcs->style,
                                    FLGSTR(WS_BORDER),
                                    FLGSTR(WS_CAPTION),
                                    FLGSTR(WS_CHILD),
                                    FLGSTR(WS_CLIPCHILDREN),
                                    FLGSTR(WS_CLIPSIBLINGS),
                                    FLGSTR(WS_DISABLED),
                                    FLGSTR(WS_DLGFRAME),
                                    FLGSTR(WS_GROUP),
                                    FLGSTR(WS_HSCROLL),
                                    FLGSTR(WS_OVERLAPPED),
#if defined(WS_OVERLAPPEDWINDOW) && (WS_OVERLAPPEDWINDOW != 0)
                                    FLGSTR(WS_OVERLAPPEDWINDOW),
#endif

#ifdef WS_ICONIC
                                    FLGSTR(WS_ICONIC),
#endif
                                    FLGSTR(WS_MAXIMIZE),
                                    FLGSTR(WS_MAXIMIZEBOX),
                                    FLGSTR(WS_MINIMIZE),
                                    FLGSTR(WS_MINIMIZEBOX),
                                    FLGSTR(WS_OVERLAPPEDWINDOW),
                                    FLGSTR(WS_POPUP),
#ifdef WS_POPUPWINDOW
                                    FLGSTR(WS_POPUPWINDOW),
#endif
                                    FLGSTR(WS_SIZEBOX),
                                    FLGSTR(WS_SYSMENU),
                                    FLGSTR(WS_TABSTOP),
                                    FLGSTR(WS_THICKFRAME),
#ifdef WS_TILED
                                    FLGSTR(WS_TILED),
#endif
#ifdef WS_TILEDWINDOW
                                    FLGSTR(WS_TILEDWINDOW),
#endif
                                    FLGSTR(WS_VISIBLE),
                                    FLGSTR(WS_VSCROLL),
                                    FLAG_STRING());

         QString exStyles = flagCheck(lpcs->dwExStyle,
#ifdef WS_EX_ACCEPTFILES
                                      FLGSTR(WS_EX_ACCEPTFILES),
#endif
#ifdef WS_EX_APPWINDOW
                                      FLGSTR(WS_EX_APPWINDOW),
#endif
                                      FLGSTR(WS_EX_CLIENTEDGE),
                                      FLGSTR(WS_EX_DLGMODALFRAME),
#ifdef WS_EX_LEFT
                                      FLGSTR(WS_EX_LEFT),
#endif
                                      FLGSTR(WS_EX_LEFTSCROLLBAR),
#ifdef WS_EX_LTRREADING
                                      FLGSTR(WS_EX_LTRREADING),
#endif
#ifdef WS_EX_MDICHILD
                                      FLGSTR(WS_EX_MDICHILD),
#endif
#ifdef WS_EX_NOACTIVATE
                                      FLGSTR(WS_EX_NOACTIVATE),
#endif
#ifdef WS_EX_NOANIMATION
                                      FLGSTR(WS_EX_NOANIMATION),
#endif
                                      FLGSTR(WS_EX_NOPARENTNOTIFY),
                                      FLGSTR(WS_EX_OVERLAPPEDWINDOW),
#ifdef WS_EX_PALETTEWINDOW
                                      FLGSTR(WS_EX_PALETTEWINDOW),
#endif
#ifdef WS_EX_RIGHT
                                      FLGSTR(WS_EX_RIGHT),
#endif
#ifdef WS_EX_RIGHTSCROLLBAR
                                      FLGSTR(WS_EX_RIGHTSCROLLBAR),
#endif
#ifdef WS_EX_RTLREADING
                                      FLGSTR(WS_EX_RTLREADING),
#endif
                                      FLGSTR(WS_EX_STATICEDGE),
                                      FLGSTR(WS_EX_TOOLWINDOW),
                                      FLGSTR(WS_EX_TOPMOST),
#ifdef WS_EX_TRANSPARENT
                                      FLGSTR(WS_EX_TRANSPARENT),
#endif
                                      FLGSTR(WS_EX_WINDOWEDGE),
#ifdef WS_EX_CAPTIONOKBTN
                                      FLGSTR(WS_EX_CAPTIONOKBTN),
#endif
                                      FLAG_STRING());

         QString className;

         if (lpcs->lpszClass != nullptr) {
            if (HIWORD(lpcs->lpszClass) == 0) {
               // Atom
               className = QString::number(LOWORD(lpcs->lpszClass), 16);

            } else {
               // string
               className = QString((QChar *)lpcs->lpszClass, (int)wcslen(reinterpret_cast<const wchar_t *>(lpcs->lpszClass)));
            }
         }

         QString windowName;

         if (lpcs->lpszName != nullptr) {
            windowName = QString((QChar *)lpcs->lpszName, (int)wcslen(reinterpret_cast<const wchar_t *>(lpcs->lpszName)));
         }

         parameters = QString("x,y(%1,%2) w,h(%3,%4) className(%5) windowName(%6) parent(0x%7) style(%8) exStyle(%9)")
                .formatArg(lpcs->x, 4).formatArg(lpcs->y, 4).formatArg(lpcs->cx, 4).formatArg(lpcs->cy, 4)
                .formatArg(className).formatArg(windowName).formatArg(reinterpret_cast<quintptr>(lpcs->hwndParent), 8, 16, '0')
                .formatArg(styles).formatArg(exStyles);
      }
      break;

#endif

#ifdef WM_DESTROY
      case WM_DESTROY:
         parameters = QString("Destroy hwnd (0x%1)").formatArg(reinterpret_cast<quintptr>(msg.hwnd), 8, 16, '0');
         break;
#endif

#ifdef WM_IME_NOTIFY
      case WM_IME_NOTIFY: {
         QString imnCommand = valueCheck(wParam,
                                         FLGSTR(IMN_CHANGECANDIDATE),
                                         FLGSTR(IMN_CLOSECANDIDATE),
                                         FLGSTR(IMN_CLOSESTATUSWINDOW),
                                         FLGSTR(IMN_GUIDELINE),
                                         FLGSTR(IMN_OPENCANDIDATE),
                                         FLGSTR(IMN_OPENSTATUSWINDOW),
                                         FLGSTR(IMN_SETCANDIDATEPOS),
                                         FLGSTR(IMN_SETCOMPOSITIONFONT),
                                         FLGSTR(IMN_SETCOMPOSITIONWINDOW),
                                         FLGSTR(IMN_SETCONVERSIONMODE),
                                         FLGSTR(IMN_SETOPENSTATUS),
                                         FLGSTR(IMN_SETSENTENCEMODE),
                                         FLGSTR(IMN_SETSTATUSWINDOWPOS),
                                         FLAG_STRING());

         parameters = QString("Command(%1 : 0x%2)").formatArg(imnCommand).formatArg(lParam, 8, 16, '0');
      }
      break;
#endif

#ifdef WM_IME_SETCONTEXT
      case WM_IME_SETCONTEXT: {
         bool fSet = (BOOL)wParam;
         DWORD fShow = (DWORD)lParam;

         QString showFlgs = flagCheck(fShow,
#ifdef ISC_SHOWUICOMPOSITIONWINDOW
                                      FLGSTR(ISC_SHOWUICOMPOSITIONWINDOW),
#endif

#ifdef ISC_SHOWUIGUIDWINDOW
                                      FLGSTR(ISC_SHOWUIGUIDWINDOW),
#endif

#ifdef ISC_SHOWUISOFTKBD
                                      FLGSTR(ISC_SHOWUISOFTKBD),
#endif
                                      FLGSTR(ISC_SHOWUICANDIDATEWINDOW),
                                      FLGSTR(ISC_SHOWUICANDIDATEWINDOW << 1),
                                      FLGSTR(ISC_SHOWUICANDIDATEWINDOW << 2),
                                      FLGSTR(ISC_SHOWUICANDIDATEWINDOW << 3),
                                      FLAG_STRING());

         parameters = QString("Input context(%1) Show flags(%2)").formatArg(fSet ? QString("Active") : QString("Inactive")).formatArg(showFlgs);
      }
      break;
#endif

#ifdef WM_KILLFOCUS
      case WM_KILLFOCUS:
         parameters = QString("Hwnd gaining keyboard focus (0x%1)").formatArg(wParam, 8, 16, '0');
         break;
#endif

#ifdef WM_CHAR
      case WM_CHAR:
#endif

#ifdef WM_IME_CHAR
      case WM_IME_CHAR:
#endif

#ifdef WM_KEYDOWN
      case WM_KEYDOWN:
#endif

#ifdef WM_KEYUP
      case WM_KEYUP: {
         int nVirtKey     = (int)wParam;
         long lKeyData    = (long)lParam;
         int repCount     = (lKeyData & 0xffff);        // Bit 0-15
         int scanCode     = (lKeyData & 0xf0000) >> 16; // Bit 16-23
         bool contextCode = (lKeyData & 0x20000000);    // Bit 29
         bool prevState   = (lKeyData & 0x40000000);    // Bit 30
         bool transState  = (lKeyData & 0x80000000);    // Bit 31

         parameters = QString("Virtual-key(0x%1) Scancode(%2) Rep(%3) Contextcode(%4), Prev state(%5), Trans state(%6)")
                  .formatArg(nVirtKey, 0, 16).formatArg(scanCode).formatArg(repCount).formatArg(contextCode)
                  .formatArg(prevState).formatArg(transState);
      }
      break;
#endif

#ifdef WM_INPUTLANGCHANGE
      case WM_INPUTLANGCHANGE:
         parameters = QLatin1String("Keyboard layout changed");
         break;
#endif

#ifdef WM_NCACTIVATE
      case WM_NCACTIVATE: {
         parameters = (msg.wParam ? QString("Active Titlebar") : QString("Inactive Titlebar"));
      }
      break;
#endif

#ifdef WM_MOUSEACTIVATE
      case WM_MOUSEACTIVATE: {
         QString mouseMsg = QString::fromLatin1(findWMstr(HIWORD(lParam)));
         parameters = QString("TLW(0x%1) HittestCode(0x%2) MouseMsg(%3)")
                  .formatArg(wParam, 8, 16, '0').formatArg(LOWORD(lParam)).formatArg(mouseMsg);

      }
      break;
#endif

#ifdef WM_MOUSELEAVE
      case WM_MOUSELEAVE:
         break; // wParam & lParam not used
#endif
#ifdef WM_MOUSEHOVER
      case WM_MOUSEHOVER:
#endif
#ifdef WM_MOUSEWHEEL
      case WM_MOUSEWHEEL:
#endif
#ifdef WM_MOUSEHWHEEL
      case WM_MOUSEHWHEEL:
#endif
#ifdef WM_LBUTTONDBLCLK
      case WM_LBUTTONDBLCLK:
#endif
#ifdef WM_LBUTTONDOWN
      case WM_LBUTTONDOWN:
#endif
#ifdef WM_LBUTTONUP
      case WM_LBUTTONUP:
#endif
#ifdef WM_MBUTTONDBLCLK
      case WM_MBUTTONDBLCLK:
#endif
#ifdef WM_MBUTTONDOWN
      case WM_MBUTTONDOWN:
#endif
#ifdef WM_MBUTTONUP
      case WM_MBUTTONUP:
#endif
#ifdef WM_RBUTTONDBLCLK
      case WM_RBUTTONDBLCLK:
#endif
#ifdef WM_RBUTTONDOWN
      case WM_RBUTTONDOWN:
#endif
#ifdef WM_RBUTTONUP
      case WM_RBUTTONUP:
#endif
#ifdef WM_MOUSEMOVE
      case WM_MOUSEMOVE: {
         QString vrtKeys = flagCheck(wParam,
                                     FLGSTR(MK_CONTROL),
                                     FLGSTR(MK_LBUTTON),
                                     FLGSTR(MK_MBUTTON),
                                     FLGSTR(MK_RBUTTON),
                                     FLGSTR(MK_SHIFT),
#ifdef MK_XBUTTON1
                                     FLGSTR(MK_XBUTTON1),
#endif
#ifdef MK_XBUTTON2
                                     FLGSTR(MK_XBUTTON2),
#endif
                                     FLAG_STRING());

         parameters = QString("x,y(%1,%2) Virtual Keys(%3)").formatArg(GET_X_LPARAM(lParam), 4)
                  .formatArg(GET_Y_LPARAM(lParam), 4).formatArg(vrtKeys);
      }
      break;
#endif

#ifdef WM_MOVE
      case WM_MOVE:
         parameters = QString("x,y(%1,%2)").formatArg(LOWORD(lParam), 4).formatArg(HIWORD(lParam), 4);
         break;
#endif

#if defined(WM_PAINT) && defined(WM_ERASEBKGND)
      case WM_ERASEBKGND:
      case WM_PAINT:
         parameters = QString("hdc(0x%1)").formatArg(wParam, 8, 16, '0');
         break;
#endif

#ifdef WM_QUERYNEWPALETTE
      case WM_QUERYNEWPALETTE:
         break; // lParam & wParam are unused
#endif

#ifdef WM_SETCURSOR
      case WM_SETCURSOR: {
         QString mouseMsg = QString::fromLatin1(findWMstr(HIWORD(lParam)));
         parameters = QString("HitTestCode(0x%1) MouseMsg(%2)").formatArg(LOWORD(lParam), 0, 16).formatArg(mouseMsg);
      }
      break;
#endif

#ifdef WM_SETFOCUS
      case WM_SETFOCUS:
         parameters = QString("Lost Focus (0x%1)").formatArg(wParam, 8, 16, '0');
         break;
#endif

#ifdef WM_SETTEXT
      case WM_SETTEXT:
         {
            QString tmp = QString::fromStdWString(std::wstring((const wchar_t *)lParam));
            parameters = QString("Set Text (%1)").formatArg(tmp);
            break;
         }
#endif

#ifdef WM_SIZE
      case WM_SIZE: {
         QString showMode = valueCheck(wParam,
                                       FLGSTR(SIZE_MAXHIDE),
                                       FLGSTR(SIZE_MAXIMIZED),
                                       FLGSTR(SIZE_MAXSHOW),
                                       FLGSTR(SIZE_MINIMIZED),
                                       FLGSTR(SIZE_RESTORED),
                                       FLAG_STRING());

         parameters = QString("w,h(%1,%2) showmode(%3)").formatArg(LOWORD(lParam), 4).formatArg(HIWORD(lParam), 4).formatArg(showMode);

      }
      break;
#endif

#ifdef WM_WINDOWPOSCHANGED
      case WM_WINDOWPOSCHANGED: {
         LPWINDOWPOS winPos = (LPWINDOWPOS)lParam;

         if (!winPos) {
            break;
         }
         QString hwndAfter = valueCheck(quint64(winPos->hwndInsertAfter),
                                        FLAG_STRING((qptrdiff)HWND_BOTTOM,    "HWND_BOTTOM"),
                                        FLAG_STRING((qptrdiff)HWND_NOTOPMOST, "HWND_NOTOPMOST"),
                                        FLAG_STRING((qptrdiff)HWND_TOP,       "HWND_TOP"),
                                        FLAG_STRING((qptrdiff)HWND_TOPMOST,   "HWND_TOPMOST"),
                                        FLAG_STRING());
         if (hwndAfter.isEmpty()) {
            hwndAfter = QString::number((quintptr)winPos->hwndInsertAfter, 16);
         }

         QString flags = flagCheck(winPos->flags,
                                   FLGSTR(SWP_DRAWFRAME),
                                   FLGSTR(SWP_FRAMECHANGED),
                                   FLGSTR(SWP_HIDEWINDOW),
                                   FLGSTR(SWP_NOACTIVATE),
#ifdef SWP_NOCOPYBITS
                                   FLGSTR(SWP_NOCOPYBITS),
#endif
                                   FLGSTR(SWP_NOMOVE),
                                   FLGSTR(SWP_NOOWNERZORDER),
                                   FLGSTR(SWP_NOREDRAW),
                                   FLGSTR(SWP_NOREPOSITION),
#ifdef SWP_NOSENDCHANGING
                                   FLGSTR(SWP_NOSENDCHANGING),
#endif
                                   FLGSTR(SWP_NOSIZE),
                                   FLGSTR(SWP_NOZORDER),
                                   FLGSTR(SWP_SHOWWINDOW),
                                   FLAG_STRING());

         parameters = QString("x,y(%1,%2) w,h(%3,%4) flags(%5) hwndAfter(%6)")
                  .formatArg(winPos->x, 4).formatArg(winPos->y, 4).formatArg(winPos->cx, 4).formatArg(winPos->cy, 4)
                  .formatArg(flags).formatArg(hwndAfter);
      }
      break;
#endif

      default:
         parameters = QString("wParam(0x%1) lParam(0x%2").formatArg(wParam, 8, 16, '0').formatArg(lParam, 8, 16, '0');
         break;
   }

   // we want to give the WM_ names 20 chars of space before showing the
   // decoded message, since some of the common messages are quite long, and
   // we don't want the decoded information to vary in output position

   QString message = QString::fromLatin1("%1: ").formatArg(wmmsg, 20);

   message += rawParameters;
   message += parameters;
   return message;
}


QDebug operator<<(QDebug dbg, const MSG &msg)
{
   dbg << decodeMSG(msg);
   return dbg.nospace();
}
#endif

